package com.xu.cloud.common.util;

import lombok.Data;
import org.lionsoul.ip2region.xdb.Searcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
import org.springframework.util.StreamUtils;

import javax.servlet.http.HttpServletRequest;
import java.net.*;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 获取IP地址
 *
 * @author xuguan
 * @since 2023/7/28
 */
public class IpUtil {

    public static final Logger log = LoggerFactory.getLogger(IpUtil.class);

    public static final String LOCAL_IP = "127.0.0.1";

    public static final String API_COM_HOST = "http://ip-api.com";

    private static final List<String> HEADERS = Stream.of(
                    "X-Forwarded-For",
                    "X-Real-IP",
                    "Proxy-Client-IP",
                    "WL-Proxy-Client-IP",
                    "HTTP_CLIENT_IP",
                    "HTTP_X_FORWARDED_FOR",
                    "HTTP_X_FORWARDED",
                    "HTTP_X_CLUSTER_CLIENT_IP",
                    "HTTP_FORWARDED_FOR",
                    "HTTP_FORWARDED",
                    "HTTP_VIA",
                    "REMOTE_ADDR"
            )
            .collect(Collectors.toList());

    /**
     * 获取客户端IP地址
     */
    public static String getClientIp(HttpServletRequest request) {
        String ip;
        for (String header : HEADERS) {
            ip = request.getHeader(header);
            if (!isUnknown(ip)) {
                // 如果有多个代理地址, 取第一个地址
                ip = getMultistageReverseProxyIp(ip);
                if (!isUnknown(ip)) {
                    return ip;
                }
            }
        }
        ip = request.getRemoteAddr();

        return isLocalIp(ip)
                ? LOCAL_IP
                : getMultistageReverseProxyIp(ip);
    }

    /**
     * 获取本地IP地址
     */
    public static String getLocalIp() {
        String ip = "";
        try {
            Enumeration<NetworkInterface> ifces = NetworkInterface.getNetworkInterfaces();
            while (ifces.hasMoreElements()) {
                NetworkInterface ifc = ifces.nextElement();
                if (ifc.isLoopback() || ifc.isVirtual() || !ifc.isUp()) {
                    continue;
                }

                Enumeration<InetAddress> addresses = ifc.getInetAddresses();
                while (addresses.hasMoreElements()) {
                    InetAddress address = addresses.nextElement();
                    if (address != null && !address.isLoopbackAddress() && address instanceof Inet4Address) {
                        if (address.isSiteLocalAddress()) {
                            return address.getHostAddress();
                        }
                        if ("".equals(ip)) {
                            ip = address.getHostAddress();
                        }
                    }
                }
            }
        } catch (SocketException e) {
            log.info("获取网卡信息失败");
        }

        if ("".equals(ip)) {
            try {
                ip = InetAddress.getLocalHost().getHostAddress();
            } catch (UnknownHostException e) {
                log.info("获取本地IP地址失败");
                ip = "127.0.0.1";
            }
        }

        return ip;
    }

    /**
     * 判断ip地址是否为中国地址
     */
    public static boolean isChinaIp(String ip) {
        IpRegion ipRegion = parseIpRegion(ip);
        return "CN".equalsIgnoreCase(ipRegion.getCountryCode())
                || "China".equalsIgnoreCase(ipRegion.getCountry())
                || "中国".equalsIgnoreCase(ipRegion.getCountry());
    }

    /**
     * 判断ip地址是否为内网地址
     * 10.0.0.0 - 10.255.255.255
     * 172.16.0.0 - 172.31.255.255
     * 192.168.0.0 - 192.168.255.255
     */
    public static boolean isInternalIp(String ip) {
        final String[] ipSplitArr = ip.split("\\.", 4);
        final int first = Integer.parseInt(ipSplitArr[0]);
        final int second = Integer.parseInt(ipSplitArr[1]);
        final int third = Integer.parseInt(ipSplitArr[2]);
        final int fourth = Integer.parseInt(ipSplitArr[3]);
        return (first == 10)
                || (first == 172 && second >= 16 && second <= 31)
                || (first == 192 && second == 168)
                || (first == 127);
    }

    /**
     * 解析ip地址
     */
    public static IpRegion parseIpRegion(String ip) {
        final long start = System.currentTimeMillis();
        final ClassLoader clr = IpUtil.class.getClassLoader();
        IpRegion ipRegion = null;
        if (ClassUtils.isPresent("org.lionsoul.ip2region.xdb.Searcher", clr)) {
            // 国家|区域|省份|城市|ISP
            String search = null;
            try {
                ClassPathResource cpr = new ClassPathResource("ip2region.xdb");
                final Searcher searcher = Searcher.newWithBuffer(StreamUtils.copyToByteArray(cpr.getInputStream()));
                search = searcher.search(ip);
                final String[] searchArr = search.split("\\|", -1);
                ipRegion = new IpRegion();
                ipRegion.setQuery(ip);
                ipRegion.setCountry(searchArr[0]);
                ipRegion.setRegionName(searchArr[2]);
                ipRegion.setCity(searchArr[3]);
                ipRegion.setIsp(searchArr[4]);
            } catch (Exception e) {
                log.error("ip2region解析失败", e);
            }
        }

        if (Objects.isNull(ipRegion)) {
            ipRegion = RestTemplateUtil.doGet(API_COM_HOST + "/json/" + ip, IpRegion.class);
        }

        final long end = System.currentTimeMillis();
        log.info("ip2region解析完成, ipRegion: {}, 耗时: {}ms", ipRegion, (end - start));
        return ipRegion;
    }

    /**
     * 获取多级反向代理IP地址
     */
    @Nullable
    private static String getMultistageReverseProxyIp(@Nullable String ip) {
        if (Objects.nonNull(ip) && ip.contains(",")) {
            ip = Arrays.stream(ip.split(","))
                    .filter(i -> !isUnknown(i))
                    .findFirst()
                    .orElse(null);
        }
        return ip;
    }

    private static boolean isUnknown(@Nullable String ip) {
        return Objects.isNull(ip) || "".equals(ip.trim()) || "unknown".equalsIgnoreCase(ip);
    }

    private static boolean isLocalIp(@Nullable String ip) {
        if (Objects.isNull(ip)) {
            return false;
        }
        return "localhost".equalsIgnoreCase(ip)
                || "127.0.0.1".equalsIgnoreCase(ip)
                || "0:0:0:0:0:0:0:1".equalsIgnoreCase(ip);
    }

    @Data
    public static class IpRegion {
        private String status;
        private String country;
        private String countryCode;
        private String region;
        private String regionName;
        private String city;
        private String zip;
        private String lat;
        private String lon;
        private String timezone;
        private String isp;
        private String org;
        private String as;
        private String query;
    }

    public static void main(String[] args) {
        System.out.println(IpUtil.parseIpRegion("127.0.24.86"));
    }
}
